package com.chiancloud.shield.server.services.curator;

import com.chiancloud.shield.core.persist.JsonSerDeser;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.x.discovery.*;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URL;
import java.util.*;

/**
 * Created by wanghui on 14-8-20.
 */

public class RegistryBinderService<Payload> extends CuratorService {
    private static final Logger log =
            LoggerFactory.getLogger(RegistryBinderService.class);

    private final ServiceDiscovery<Payload> discovery;

    private final Map<String, ServiceInstance<Payload>> entries =
            new HashMap<String, ServiceInstance<Payload>>();

    private JsonSerDeser<CuratorServiceInstance<Payload>> deser =
            new JsonSerDeser<CuratorServiceInstance<Payload>>(CuratorServiceInstance.class);


    public RegistryBinderService(CuratorFramework curator,
                                 String basePath,
                                 ServiceDiscovery<Payload> discovery) {
        super("RegistryBinderService", curator, basePath);

        this.discovery =
                Preconditions.checkNotNull(discovery, "null discovery arg");
    }


    public ServiceDiscovery<Payload> getDiscovery() {
        return discovery;
    }

    @Override
    protected void serviceStart() throws Exception {
        super.serviceStart();
        discovery.start();
    }

    @Override
    protected void serviceStop() throws Exception {
        closeCuratorComponent(discovery);
        super.serviceStop();
    }


    public ServiceInstance<Payload> register(String name,
                                             String id,
                                             URL url,
                                             Payload payload) throws Exception {
        Preconditions.checkNotNull(id, "null `id` arg");
        Preconditions.checkNotNull(name, "null `name` arg");
        Preconditions.checkState(isInState(STATE.STARTED), "Not started: " + this);

        ServiceInstanceBuilder<Payload> instanceBuilder = builder()
                .name(name)
                .id(id)
                .payload(payload)
                .serviceType(ServiceType.DYNAMIC);
        if (url != null) {
            UriSpec uriSpec = new UriSpec(url.toString());

            int port = url.getPort();
            if (port == 0) {
                throw new IOException("Port undefined in " + url);
            }
            instanceBuilder
                    .uriSpec(uriSpec)
                    .port(port);
        }
        ServiceInstance<Payload> instance = instanceBuilder.build();
        log.info("registering {}", instance.toString());
        discovery.registerService(instance);
        log.info("registration completed {}", instance.toString());
        synchronized (this) {
            entries.put(id, instance);
        }
        return instance;
    }

    public ServiceInstanceBuilder<Payload> builder() throws
            IOException {
        try {
            return ServiceInstance.builder();
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e);
        }
    }


    public List<String> instanceIDs(String servicetype) throws Exception {
        Preconditions.checkNotNull(servicetype);
        List<String> instanceIds;
        try {
            instanceIds =
                    getCurator().getChildren().forPath(pathForServicetype(servicetype));
        } catch (KeeperException.NoNodeException e) {
            instanceIds = Lists.newArrayList();
        }
        return instanceIds;
    }


    public List<String> serviceTypes() throws Exception {
        List<String> types;
        try {
            types =
                    getCurator().getChildren().forPath(getBasePath());
        } catch (KeeperException.NoNodeException e) {
            types = Lists.newArrayList();
        }
        return types;
    }


    public CuratorServiceInstance<Payload> queryForInstance(String servicetype, String id)
            throws Exception {
        CuratorServiceInstance<Payload> instance = null;
        String path = pathForInstance(servicetype, id);
        try {
            byte[] bytes = getCurator().getData().forPath(path);
            if (bytes != null && bytes.length > 0) {
                instance = deser.fromBytes(bytes);
            }
        } catch (KeeperException.NoNodeException ignore) {
            // ignore
        }
        return instance;
    }

    public List<CuratorServiceInstance<Payload>> listInstances(String servicetype) throws
            IOException {
        try {
            List<String> instanceIDs = instanceIDs(servicetype);
            List<CuratorServiceInstance<Payload>> instances =
                    new ArrayList<>(instanceIDs.size());
            for (String instanceID : instanceIDs) {
                CuratorServiceInstance<Payload> instance =
                        queryForInstance(servicetype, instanceID);
                if (instance != null) {
                    instances.add(instance);
                }
            }
            return instances;
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e);
        }
    }

    public CuratorServiceInstance<Payload> findByID(List<CuratorServiceInstance<Payload>> instances, String name) {
        Preconditions.checkNotNull(name);
        for (CuratorServiceInstance<Payload> instance : instances) {
            if (instance.id.equals(name)) {
                return instance;
            }
        }
        return null;
    }


    public CuratorServiceInstance<Payload> findInstance(String serviceType,
                                                        String name) throws IOException {
        Preconditions.checkArgument(StringUtils.isNotEmpty(name), "name");
        return findInstances(serviceType, name).get(0);
    }

    public List<CuratorServiceInstance<Payload>> findInstances(String serviceType,
                                                               String name)
            throws FileNotFoundException, IOException {
        List<CuratorServiceInstance<Payload>> instances =
                listInstances(serviceType);
        if (instances.isEmpty()) {
            throw new FileNotFoundException(
                    "No registry entries for service type " + serviceType);
        }
        if (StringUtils.isNotEmpty(name)) {
            CuratorServiceInstance<Payload> foundInstance = findByID(instances, name);
            if (foundInstance == null) {
                throw new FileNotFoundException(
                        "No registry entries for service name " + name
                                + " and service type " + serviceType);
            }
            instances.clear();
            instances.add(foundInstance);
        }
        return instances;
    }


    public Collection<String> getServiceTypes() throws IOException {
        try {
            return getDiscovery().queryForNames();
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e);
        }
    }
}
